/*-------------------------------------------------------------------------
 *
 * shmem.c
 *	  创建共享内存并初始化共享内存数据结构。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/ipc/shmem.c
 *
 *-------------------------------------------------------------------------
 */
/*
 * POSTGRES 进程共享一个或多个共享内存区域。
 * 共享内存由后台进程创建，并通过 fork()（或在某些端口，由其他特定于操作系统的方法）被每个后端继承。此文件中的例程用于分配和绑定到共享内存数据结构。
 *
 * 备注：
 *		（a）有三种类型的共享内存数据结构可供 POSTGRES 使用：固定大小结构、队列和哈希表。固定大小结构包含模块的全局变量等内容，绝不能在共享内存初始化阶段之后分配。哈希表具有固定的最大大小，但其实际大小可以动态变化。当向表中添加条目时，会分配更多空间。队列链接的数据结构可以是在固定大小结构中分配的或作为哈希桶分配的。每个共享数据结构都有一个字符串名称来识别它（在声明它的模块中分配）。
 *
 *		（b）在初始化期间，每个模块在一个称为“Shmem Index”的哈希表中查找其共享数据结构。如果数据结构不存在，调用者可以分配一个新的并进行初始化。如果数据结构存在，调用者通过初始化本地地址空间中的指针“附加”到该结构。
 *		Shmem Index 有两个目的：首先，它给我们提供了一个简单的模型，说明后端进程初始化时的世界是什么样子。如果 shmem index 中存在某物，它就是已初始化的。如果不存在，则是未初始化的。其次，shmem index 允许我们根据需求分配共享内存，而不是尝试预分配结构并在头文件中硬编码大小和位置。如果你在很多不同的地方使用了大量共享内存（并在开发过程中修改内容），这很重要。
 *
 *		（c）在标准 Unix 风格的环境中，单个后端不需要重新建立其指向共享内存的本地指针，因为它们通过 fork() 从后台进程继承了这些变量的正确值。然而，在 EXEC_BACKEND 情况下，这并不适用。使用 EXEC_BACKEND 的端口，新的后端必须使用上述（b）中描述的方法设置它们的本地指针。
 *
 *		（d）内存分配模型：一旦分配，共享内存就不能被释放。每个哈希表都有自己的空闲列表，因此哈希桶在删除项目时可以重用。然而，如果一个哈希表变得非常大然后缩小，它的空间不能重新分配给其他表。如果需要，我们可以建立一个简单的哈希桶垃圾收集器。现在看来，这是不必要的。
 */

#include "postgres.h"

#include "access/transam.h"
#include "fmgr.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "storage/lwlock.h"
#include "storage/pg_shmem.h"
#include "storage/shmem.h"
#include "storage/spin.h"
#include "utils/builtins.h"

static void *fc_ShmemAllocRaw(Size fc_size, Size *fc_allocated_size);

/* 共享内存全局变量 */

static PGShmemHeader *ShmemSegHdr;	/* 共享内存段头 */

static void *ShmemBase;			/* 共享内存的起始地址 */

static void *ShmemEnd;			/* 共享内存的结束+1 地址 */

slock_t    *ShmemLock;			/* 共享内存和 LWLock 分配的自旋锁 */

static HTAB *ShmemIndex = NULL; /* 共享内存的主索引哈希表 */


/*
 *	InitShmemAccess() --- 设置指向共享内存的基本指针。
 *
 * 注意：参数应声明为 "PGShmemHeader *seghdr"，但我们使用 void 来避免需要在 shmem.h 中包含 ipc.h。
 */
void InitShmemAccess(void *fc_seghdr)
{
	PGShmemHeader *fc_shmhdr = (PGShmemHeader *) fc_seghdr;

	ShmemSegHdr = fc_shmhdr;
	ShmemBase = (void *) fc_shmhdr;
	ShmemEnd = (char *) ShmemBase + fc_shmhdr->totalsize;
}

/*
 *	InitShmemAllocation() --- 设置共享内存空间分配。
 *
 * 这应该仅在后端或独立后端中调用。
 */
void InitShmemAllocation(void)
{
	PGShmemHeader *fc_shmhdr = ShmemSegHdr;
	char	   *fc_aligned;

	Assert(fc_shmhdr != NULL);

	/*
	 * 初始化 ShmemAlloc 使用的自旋锁。我们必须使用
	 * ShmemAllocUnlocked，因为显然不能调用 ShmemAlloc 。
	 */
	ShmemLock = (slock_t *) ShmemAllocUnlocked(sizeof(slock_t));

	SpinLockInit(ShmemLock);

	/*
	 * 此后的分配应通过 ShmemAlloc 进行，该函数期望在缓存行边界上分配所有内容。确保
	 * 第一个分配在缓存行边界上开始。
	 */
	fc_aligned = (char *)
		(CACHELINEALIGN((((char *) fc_shmhdr) + fc_shmhdr->freeoffset)));
	fc_shmhdr->freeoffset = fc_aligned - (char *) fc_shmhdr;

	/* ShmemIndex 还不能设置（需要 LWLocks 首先） */
	fc_shmhdr->index = NULL;
	ShmemIndex = (HTAB *) NULL;

	/*
	 * 为事务管理器初始化 ShmemVariableCache。 （这实际上不属于这里，但不值得移动。）
	 */
	ShmemVariableCache = (VariableCache)
		ShmemAlloc(sizeof(*ShmemVariableCache));
	memset(ShmemVariableCache, 0, sizeof(*ShmemVariableCache));
}

/*
 * ShmemAlloc -- 从共享内存分配最大对齐的块
 *
 * 如果请求无法满足则抛出错误。
 *
 * 假设 ShmemLock 和 ShmemSegHdr 已初始化。
 */
void * ShmemAlloc(Size fc_size)
{
	void	   *fc_newSpace;
	Size		fc_allocated_size;

	fc_newSpace = fc_ShmemAllocRaw(fc_size, &fc_allocated_size);
	if (!fc_newSpace)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of shared memory (%zu bytes requested)",
						fc_size)));
	return fc_newSpace;
}

/*
 * ShmemAllocNoError -- 从共享内存分配最大对齐的块
 *
 * 与 ShmemAlloc 相同，但如果没有空间则返回 NULL，而不是抛出错误。
 */
void * ShmemAllocNoError(Size fc_size)
{
	Size		fc_allocated_size;

	return fc_ShmemAllocRaw(fc_size, &fc_allocated_size);
}

/*
 * ShmemAllocRaw -- 分配对齐块并返回分配的大小
 *
 * 还将 *allocated_size 设置为分配的字节数，这将等于请求的字节数加上我们选择添加的任何填充。
 */
static void * fc_ShmemAllocRaw(Size fc_size, Size *fc_allocated_size)
{
	Size		fc_newStart;
	Size		fc_newFree;
	void	   *fc_newSpace;

	/*
	 * 确保所有空间都得到适当对齐。 我们过去只是对该空间进行 MAXALIGN，但经历表明，在现代系统上这并不够好。
	 * 系统的许多部分对关键数据结构在缓存行边界上的分割非常敏感。 为了避免这种情况，
	 * 尝试将分配的开始对齐到缓存行边界。 调用代码仍然需要小心如何使用分配的空间 - 例如，
	 * 通过将结构数组中的每个元素填充到二的幂大小 - 但没有这一点，即使这样也不够。
	 */
	fc_size = CACHELINEALIGN(fc_size);
	*fc_allocated_size = fc_size;

	Assert(ShmemSegHdr != NULL);

	SpinLockAcquire(ShmemLock);

	fc_newStart = ShmemSegHdr->freeoffset;

	fc_newFree = fc_newStart + fc_size;
	if (fc_newFree <= ShmemSegHdr->totalsize)
	{
		fc_newSpace = (void *) ((char *) ShmemBase + fc_newStart);
		ShmemSegHdr->freeoffset = fc_newFree;
	}
	else
		fc_newSpace = NULL;

	SpinLockRelease(ShmemLock);

	/* 注意这个断言在 newSpace == NULL 时是可以的 */
	Assert(fc_newSpace == (void *) CACHELINEALIGN(fc_newSpace));

	return fc_newSpace;
}

/*
 * ShmemAllocUnlocked -- 从共享内存分配最大对齐的块
 *
 * 在不锁定 ShmemLock 的情况下分配空间。 这仅应用于在 ShmemLock 准备好之前必须发生的分配。
 *
 * 我们认为这里 maxalign 比 cachealign 足够。
 */
void * ShmemAllocUnlocked(Size fc_size)
{
	Size		fc_newStart;
	Size		fc_newFree;
	void	   *fc_newSpace;

	/*
	 * 确保分配的空间得到了适当的对齐。
	 */
	fc_size = MAXALIGN(fc_size);

	Assert(ShmemSegHdr != NULL);

	fc_newStart = ShmemSegHdr->freeoffset;

	fc_newFree = fc_newStart + fc_size;
	if (fc_newFree > ShmemSegHdr->totalsize)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of shared memory (%zu bytes requested)",
						fc_size)));
	ShmemSegHdr->freeoffset = fc_newFree;

	fc_newSpace = (void *) ((char *) ShmemBase + fc_newStart);

	Assert(fc_newSpace == (void *) MAXALIGN(fc_newSpace));

	return fc_newSpace;
}

/*
 * ShmemAddrIsValid -- 测试一个地址是否指向共享内存
 *
 * 如果指针指向共享内存段内，则返回 true。
 */
bool ShmemAddrIsValid(const void *fc_addr)
{
	return (fc_addr >= ShmemBase) && (fc_addr < ShmemEnd);
}

/*
 *	InitShmemIndex() --- 设置或附加到 shmem 索引表。
 */
void InitShmemIndex(void)
{
	HASHCTL		fc_info;

	/*
	 * 创建共享内存 shmem 索引。
	 *
	 * 由于 ShmemInitHash 调用 ShmemInitStruct，后者期望 ShmemIndex 哈希表已经存在，因此
	 * 在初始化 ShmemIndex 本身时我们有一些循环问题。 特殊的 "ShmemIndex" 哈希
	 * 表名称将告诉 ShmemInitStruct 假装存在。
	 */
	fc_info.keysize = SHMEM_INDEX_KEYSIZE;
	fc_info.entrysize = sizeof(ShmemIndexEnt);

	ShmemIndex = ShmemInitHash("ShmemIndex",
							   SHMEM_INDEX_SIZE, SHMEM_INDEX_SIZE,
							   &fc_info,
							   HASH_ELEM | HASH_STRINGS);
}

/*
 * ShmemInitHash -- 创建和初始化，或附加到，
 *		共享内存哈希表。
 *
 * 我们假设调用者正在进行某种同步，以便两个进程不会试图同时创建/初始化同一
 * 表。 （实际上，所有的创建都是在主进程中完成的；子进程应始终附加到现有表。）
 *
 * max_size 是估计的哈希表条目的最大数量。 这不是一个硬限制，但如果大幅超出，
 * 访问效率将下降（因为它用于计算目录大小，哈希表桶将变得过满）。
 *
 * init_size 是要预分配的哈希表条目数量。 对于最大尺寸肯定的表，此值应等于 max_size；
 * 这确保不会发生运行时的共享内存不足故障。
 *
 * *infoP 和 hash_flags 必须至少指定条目大小和键
 * 比较语义（参见 hash_create()）。 此处添加了特定于共享内存哈希表的标志位和特定值，
 * 除非调用者选择指定 HASH_PARTITION 和/或 HASH_FIXED_SIZE。
 *
 * 注意：在 Postgres 9.0 之前，此函数在某些失败情况下返回 NULL。
 * 现在，它总是抛出错误，因此调用者无需检查 NULL。
 */
HTAB * ShmemInitHash(const char *fc_name,		/* shmem 索引的表字符串名称 */
			  long fc_init_size,	/* 初始表大小 */
			  long fc_max_size,	/* 表的最大大小 */
			  HASHCTL *fc_infoP,	/* 关于键和桶大小的信息 */
			  int fc_hash_flags)	/* 关于 infoP 的信息 */
{
	bool		fc_found;
	void	   *fc_location;

	/*
	 * 在共享内存中分配的哈希表具有固定目录；它不能
	 * 增长，否则其他后端将无法找到它。因此，请确保我们一开始就使其足够大。
	 *
	 * 共享内存分配器也必须指定。
	 */
	fc_infoP->dsize = fc_infoP->max_dsize = hash_select_dirsize(fc_max_size);
	fc_infoP->alloc = ShmemAllocNoError;
	fc_hash_flags |= HASH_SHARED_MEM | HASH_ALLOC | HASH_DIRSIZE;

	/* 在 shmem 索引中查找它 */
	fc_location = ShmemInitStruct(fc_name,
							   hash_get_shared_size(fc_infoP, fc_hash_flags),
							   &fc_found);

	/*
	 * 如果它已经存在，附加到它，而不是分配和初始化
	 * 新空间
	 */
	if (fc_found)
		fc_hash_flags |= HASH_ATTACH;

	/* 将哈希表头的位置传递给 hash_create */
	fc_infoP->hctl = (HASHHDR *) fc_location;

	return hash_create(fc_name, fc_init_size, fc_infoP, fc_hash_flags);
}


/*******************************************
 * ShmemInitStruct -- 在共享内存中创建/附加到一个结构体。
 *
 *       这在初始化期间被调用，以查找或分配
 *       一个在共享内存中的数据结构。如果没有其他进程
 *       创建该结构，这个例程为其分配空间。
 *       如果已存在，则返回指向现有
 *       结构的指针。
 *
 *       返回：指向对象的指针。如果对象已经在
 *       shmem 索引中（因此，已经初始化），*foundPtr 会被设置为 true。
 *
 *       注意：在 Postgres 9.0 之前，此函数在某些失败情况下返回 NULL。
 *       现在，它总是抛出错误，因此调用者无需检查
 *       NULL。
*******************************************/
void * ShmemInitStruct(const char *fc_name, Size fc_size, bool *fc_foundPtr)
{
	ShmemIndexEnt *fc_result;
	void	   *fc_structPtr;

	LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);

	if (!ShmemIndex)
	{
		PGShmemHeader *fc_shmemseghdr = ShmemSegHdr;

		/* 必须是在尝试创建/附加到 ShmemIndex 本身 */
		Assert(strcmp(fc_name, "ShmemIndex") == 0);

		if (IsUnderPostmaster)
		{
			/* 必须是在初始化一个（非独立）后端 */
			Assert(fc_shmemseghdr->index != NULL);
			fc_structPtr = fc_shmemseghdr->index;
			*fc_foundPtr = true;
		}
		else
		{
			/*
			 * 如果 shmem 索引不存在，我们正在引导：我们必须
			 * 尝试初始化 shmem 索引本身。
			 *
			 * 注意，在 shmem
			 * 索引初始化之前释放 ShmemIndexLock。这应该没问题，因为
			 * 还没有其他进程可以访问共享内存。
			 */
			Assert(fc_shmemseghdr->index == NULL);
			fc_structPtr = ShmemAlloc(fc_size);
			fc_shmemseghdr->index = fc_structPtr;
			*fc_foundPtr = false;
		}
		LWLockRelease(ShmemIndexLock);
		return fc_structPtr;
	}

	/* 在 shmem 索引中查找它 */
	fc_result = (ShmemIndexEnt *)
		hash_search(ShmemIndex, fc_name, HASH_ENTER_NULL, fc_foundPtr);

	if (!fc_result)
	{
		LWLockRelease(ShmemIndexLock);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("could not create ShmemIndex entry for data structure \"%s\"",
						fc_name)));
	}

	if (*fc_foundPtr)
	{
		/*
		 * 结构在 shmem 索引中，所以其他人已经分配了它。
		 * 大小最好与我们试图初始化的大小相同，
		 * 否则就会出现名称冲突（或更糟）。
		 */
		if (fc_result->size != fc_size)
		{
			LWLockRelease(ShmemIndexLock);
			ereport(ERROR,
					(errmsg("ShmemIndex entry size is wrong for data structure"
							" \"%s\": expected %zu, actual %zu",
							fc_name, fc_size, fc_result->size)));
		}
		fc_structPtr = fc_result->location;
	}
	else
	{
		Size		fc_allocated_size;

		/* 它还不在表中。分配并初始化它 */
		fc_structPtr = fc_ShmemAllocRaw(fc_size, &fc_allocated_size);
		if (fc_structPtr == NULL)
		{
			/* 内存不足；移除失败的 ShmemIndex 条目 */
			hash_search(ShmemIndex, fc_name, HASH_REMOVE, NULL);
			LWLockRelease(ShmemIndexLock);
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("not enough shared memory for data structure"
							" \"%s\" (%zu bytes requested)",
							fc_name, fc_size)));
		}
		fc_result->size = fc_size;
		fc_result->allocated_size = fc_allocated_size;
		fc_result->location = fc_structPtr;
	}

	LWLockRelease(ShmemIndexLock);

	Assert(ShmemAddrIsValid(fc_structPtr));

	Assert(fc_structPtr == (void *) CACHELINEALIGN(fc_structPtr));

	return fc_structPtr;
}


/*
 * 添加两个 Size 值，检查溢出
 */
Size add_size(Size fc_s1, Size fc_s2)
{
	Size		fc_result;

	fc_result = fc_s1 + fc_s2;
	/* 我们假设Size是一个无符号类型... */
	if (fc_result < fc_s1 || fc_result < fc_s2)
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("requested shared memory size overflows size_t")));
	return fc_result;
}

/*
 * 乘以两个Size值，检查溢出
 */
Size mul_size(Size fc_s1, Size fc_s2)
{
	Size		fc_result;

	if (fc_s1 == 0 || fc_s2 == 0)
		return 0;
	fc_result = fc_s1 * fc_s2;
	/* 我们假设Size是一个无符号类型... */
	if (fc_result / fc_s2 != fc_s1)
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("requested shared memory size overflows size_t")));
	return fc_result;
}

/* SQL SRF 显示已分配的共享内存 */
Datum pg_get_shmem_allocations(PG_FUNCTION_ARGS)
{
#define PG_GET_SHMEM_SIZES_COLS 4
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	HASH_SEQ_STATUS fc_hstat;
	ShmemIndexEnt *fc_ent;
	Size		fc_named_allocated = 0;
	Datum		fc_values[PG_GET_SHMEM_SIZES_COLS];
	bool		fc_nulls[PG_GET_SHMEM_SIZES_COLS];

	InitMaterializedSRF(fcinfo, 0);

	LWLockAcquire(ShmemIndexLock, LW_SHARED);

	hash_seq_init(&fc_hstat, ShmemIndex);

	/* 输出所有分配的条目 */
	memset(fc_nulls, 0, sizeof(fc_nulls));
	while ((fc_ent = (ShmemIndexEnt *) hash_seq_search(&fc_hstat)) != NULL)
	{
		fc_values[0] = CStringGetTextDatum(fc_ent->key);
		fc_values[1] = Int64GetDatum((char *) fc_ent->location - (char *) ShmemSegHdr);
		fc_values[2] = Int64GetDatum(fc_ent->size);
		fc_values[3] = Int64GetDatum(fc_ent->allocated_size);
		fc_named_allocated += fc_ent->allocated_size;

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc,
							 fc_values, fc_nulls);
	}

	/* 输出已分配但未通过shmem索引计数的共享内存 */
	fc_values[0] = CStringGetTextDatum("<anonymous>");
	fc_nulls[1] = true;
	fc_values[2] = Int64GetDatum(ShmemSegHdr->freeoffset - fc_named_allocated);
	fc_values[3] = fc_values[2];
	tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc, fc_values, fc_nulls);

	/* 输出尚未使用的共享内存 */
	fc_nulls[0] = true;
	fc_values[1] = Int64GetDatum(ShmemSegHdr->freeoffset);
	fc_nulls[1] = false;
	fc_values[2] = Int64GetDatum(ShmemSegHdr->totalsize - ShmemSegHdr->freeoffset);
	fc_values[3] = fc_values[2];
	tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc, fc_values, fc_nulls);

	LWLockRelease(ShmemIndexLock);

	return (Datum) 0;
}
